package feevale.dropbox.util;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

import feevale.dropbox.main.Server;

/**
 * Classe para representar os clientes conectados ao servidor.
 *
 */
public class Connection implements Runnable {
	
	private static final String SEPARATOR = "|";
	private static final String ESCAPE_SEPARATOR = "\\|";
	
	/**
	 * Tamanho default do buffer utilizado.
	 */
	private static final Integer BUFFER_SIZE = 1024;

	private Socket socket;
	private Thread thread;
	public DataOutputStream out;
	public DataInputStream in;
	
	/**
	 * Indica se irá mostrar mensagens de debug durante o processo
	 */
	private static boolean debugging = false;

	public Connection(Socket socket) {
		this.socket = socket;
		this.thread = new Thread(this);
	}
	
	public void setup() {
		try {
			
			this.in = new DataInputStream(this.socket.getInputStream());
			this.out = new DataOutputStream(this.socket.getOutputStream());
			this.thread.start();
		
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() {
		
		int n = 0;
		byte[] buffer = new byte[BUFFER_SIZE];
		
		try {
			
			while ((n = in.read(buffer)) > -1) {
				
				String[] parts = getCommandParts(new String(buffer, 0, n));
				
				debug("PARTS: " + parts.length);
				for (int i = 0; i < parts.length; i++) {
					debug(i + " - " + parts[i]);
				}
				
				switch (Protocol.getValue(parts[0])) {
					case LIST:
						// envia a lista de arquivos deletados
						debug("DELETED");
						debug(getDeletedFileNamesCommand());
						
						out.write(getDeletedFileNamesCommand().getBytes());
						out.flush();
						
						// aguarda a confirmação do cliente da deleção dos arquivos
						n = 0;
						buffer = new byte[BUFFER_SIZE];
						
						while ((n = in.read(buffer)) > -1) {
							parts = getCommandParts(new String(buffer, 0, n));
							
							if (parts[0].equals(Protocol.LIST_DELETED_CONFIRMATION.getKey())) {
								break;
							}
						}
						
						// envia a lista de arquivos existentes
						debug("EXISTENT");
						debug(getExistentFileNamesCommand());
						
						out.write(getExistentFileNamesCommand().getBytes());
						out.flush();
						
						n = 0;
						buffer = new byte[BUFFER_SIZE];
						
						// aguarda a confirmação do cliente da mensagem dos arquivos existentes
						while ((n = in.read(buffer)) > -1) {
							parts = getCommandParts(new String(buffer, 0, n));
							
							if (parts[0].equals(Protocol.LIST_EXISTENT_CONFIRMATION.getKey())) {
								break;
							}
						}
						
						n = 0;
						buffer = new byte[BUFFER_SIZE];
						
						break;
						
					case DOWNLOAD:
						
						debug("downloading...");
						for (String string : parts) {
							debug(string);
						}
						
						System.out.println("ENVIANDO ARQUIVO: " + parts[2]);
						
						File downloadFile = getFileByName(parts[2]);
						
						out.write(getUploadFileCommand(downloadFile));
						out.flush();
						
						FileInputStream fIn = new FileInputStream(downloadFile);
						
						n = 0;
						buffer = new byte[BUFFER_SIZE];
						
						int outSize = 0;
						
						while (outSize < downloadFile.length()) {
							debug("entrou no while server");
							
							n = fIn.read(buffer);
							out.write(buffer, 0, n);
							out.flush();
							
							outSize += n;
						}
						
						debug("close server");
						
						fIn.close();
						
						break;
						
					case UPLOAD:
						
						System.out.println("RECEBENDO ARQUIVO: " + parts[2]);
						
						String fileName = parts[2];
						
						File fileUpload = new File(Server.FOLDER + File.separator + fileName);
						
						FileOutputStream fOut = new FileOutputStream(fileUpload);
						
						n = 0;
						buffer = new byte[BUFFER_SIZE];
						
						while (fOut.getChannel().size() < Long.parseLong(parts[3])) {
							n = in.read(buffer);
							fOut.write(buffer, 0, n);
						}
						
						// Se arquivo estiver na lista de arquivos deletados, deve excluir
						for (File file : Server.DELETED_FILES) {
							if (file.getName().equals(fileName)) {
								Server.DELETED_FILES.remove(file);
							}
						}
						
						// Adiciona na lista de arquivos existentes do servidor
						Server.EXISTENT_FILES.add(fileUpload);
//						Server.EXISTENT_FILES.put(fileUpload, this);
						
						fOut.close();
						
						out.write(Protocol.UPLOAD_CONFIRMATION.getKey().concat(SEPARATOR).getBytes());
						out.flush();
						
						break;
					case DELETE:
						System.out.println("DELETANDO ARQUIVO: " + parts[2]);
						
						File deletedFile = getFileByName(parts[2]);
						
						boolean deleted = false;
						
						if (deletedFile.exists()) {
							deleted = deletedFile.delete();
							
							debug("DELETOU ARQUIVO NO SERVER: " + deleted);
							
							Server.DELETED_FILES.add(deletedFile);
							Server.EXISTENT_FILES.remove(deletedFile);
						}
						
						out.write(Protocol.DELETE_CONFIRMATION.getKey().concat(SEPARATOR).getBytes());
						out.flush();
						
						break;
					default:
						break;
				}
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private byte[] getUploadFileCommand(File file) {
		StringBuilder builder = new StringBuilder();
		builder.append(Protocol.UPLOAD.getKey());
		builder.append(SEPARATOR);
		builder.append(formatFileNameSize(file.getName().length()));
		builder.append(SEPARATOR);
		builder.append(file.getName());
		builder.append(SEPARATOR);
		builder.append(file.length());
		builder.append(SEPARATOR);
		
		return builder.toString().getBytes();
	}
	
	private String getDeletedFileNamesCommand() {
		StringBuilder command = new StringBuilder();
		command.append(Protocol.LIST_DELETED_FILES.getKey());
		command.append(SEPARATOR);
		
		debug("@@@@@@@@@@@@@@@@ SERVER DELETED FILES SIZE: " + Server.DELETED_FILES.size());
		
		for (File file : Server.DELETED_FILES) {
			command.append(formatFileNameSize(file.getName().length()));
			command.append(SEPARATOR);
			command.append(file.getName());
			command.append(SEPARATOR);
		}
		
		return command.toString();
	}
	
	private String getExistentFileNamesCommand() {
		StringBuilder command = new StringBuilder();
		command.append(Protocol.LIST_EXISTENT_FILES.getKey());
		command.append(SEPARATOR);
		
		for (File file : Server.getFolder().listFiles()) {
			command.append(formatFileNameSize(file.getName().length()));
			command.append(SEPARATOR);
			command.append(file.getName());
			command.append(SEPARATOR);
		}
		
		return command.toString();
	}
	
	private File getFileByName(String fileName) {
		File file = null;
		
		for (File f : Server.getFolder().listFiles()) {
			if (f.getName().equals(fileName)) {
				file = f;
			}
		}
		
		return file;
	}
	
	private static String formatFileNameSize(Integer size) {
		return String.format("%04d", size);
	}
	
	private String[] getCommandParts(String txt) {
		String[] parts = txt.split(ESCAPE_SEPARATOR);
		return parts;
	}
		
	private static void debug(String log) {
		if (debugging) {
			System.out.println(new SimpleDateFormat("hh:mm:ss.SSS").format(new Date()) + " - " + log);
		}
	}

}
